/*
                  SnakeEngine Class Implementation
*/
#include "SnakeEngine.h"

vector<Player*> SnakeEngine::Players = vector<Player*>();
bool SnakeEngine::bGameRunning = false;
Map SnakeEngine::currentMap = Map();
int SnakeEngine::iLocalPlayer = -1;
SDL_Surface* SnakeEngine::World = NULL;
Timer SnakeEngine::tmrFrame = Timer();
int SnakeEngine::iTimePassed = 0;
int SnakeEngine::iCurrentKeyPress = -1;
int SnakeEngine::iSnakeStartSize = 3;
int SnakeEngine::iFoodCount = 3;
vector<Food*> SnakeEngine::FoodObjects = vector<Food*>();
vector<string> SnakeEngine::Messages = vector<string>();
vector<string> SnakeEngine::KillMessages = vector<string>();
AI SnakeEngine::engineAI = AI();
bool SnakeEngine::bNeedScoreUpdate = false;
int SnakeEngine::iNumCPUs = 0;
GameType SnakeEngine::gameType = GAMETYPE_NONE;
int SnakeEngine::iScoreToWin = 1;
int SnakeEngine::iWinner = -1;
bool SnakeEngine::bGameFinished = true;
bool SnakeEngine::bCPUOnlyGame = false;
int SnakeEngine::iCurrentDirection = UP;

void SnakeEngine::Quit()
{
    for (int i = 0; i < Players.size(); i++)
    {
        delete Players.at(i);
    }

    Players.clear();

    for (int i = 0; i < FoodObjects.size(); i++)
    {
        delete FoodObjects.at(i);
    }

    FoodObjects.clear();

    bGameRunning = false;
    bGameFinished = true;
    bCPUOnlyGame = false;
    iLocalPlayer = -1;
    iSnakeStartSize = 3;
    iScoreToWin = 1;
    iWinner = -1;

    SDL_FreeSurface(World);
    World = NULL;

    tmrFrame.Stop();

    gameType = GAMETYPE_NONE;

    engineAI.Quit();
}

int SnakeEngine::GameSetup(const string& strMap, const int& iStartSize, const GameType& NewGametype, const int& iNewFoodCount, const int& iNewScoreToWin)
{
    if (bGameRunning == true) { return -1; }

    if (currentMap.ImportFromXML("data//maps//" + strMap + "//map.pmf") == -1)
    {
        return -1;
    }

    iSnakeStartSize = iStartSize;
    gameType = NewGametype;

    iFoodCount = iNewFoodCount;
    iNumCPUs = 0;
    iScoreToWin = (iNewScoreToWin > 0) ? iNewScoreToWin : 1;

    bGameFinished = false;

    return 0;
}

int SnakeEngine::StartGame()
{
    // If a game is running already or if there is no local player then we can't start a game
    if (bGameRunning == true || gameType == GAMETYPE_NONE)
    {
        return -1;
    }

    if (iLocalPlayer == -1)
    {
        if (Players.size() > 0)
        {
            iLocalPlayer = 0;
            bCPUOnlyGame = true;
        }
        else
        {
            return -1;
        }
    }
    else
    {
        if (iLocalPlayer >= Players.size())
        {
            return -1;
        }
    }

    if (World != NULL) { SDL_FreeSurface(World); }

    Camera::GetView().w = ConfigFile::iCurrentWindowResX;
    Camera::GetView().h = ConfigFile::iCurrentWindowResY;

    World = OptimiseSurface(SDL_CreateRGBSurface(SDL_HWSURFACE | SDL_SRCALPHA, currentMap.getWidth(), currentMap.getHeight(), 32, RMASK, GMASK, BMASK, AMASK));

    SDL_Rect Worldrect;

    Worldrect.x = 0;
    Worldrect.y = 0;
    Worldrect.w = World->w;
    Worldrect.h = World->h;

    if ((currentMap.getWidth() >= ConfigFile::iCurrentWindowResX) && (currentMap.getHeight() >= ConfigFile::iCurrentWindowResY))
    {
        Camera::Init(Point(0, 0), true, Worldrect, MOVEMENTINTERVAL);
    }
    else
    {
        Camera::Init(Point(0, 0), false, Worldrect, MOVEMENTINTERVAL);
    }

    Camera::Update(Players.at(iLocalPlayer)->getPlayerRect());

    for (int i = 0; i < iFoodCount; i++)
    {
        FoodObjects.push_back(new Food);
        FoodObjects.back()->setType(rand() % FoodInfo::getNumTypes());
        FoodObjects.back()->setPosition(currentMap.FindSpawnPosition());
    }

    bGameRunning = true;

    tmrFrame.Start();

    engineAI.Initialise(currentMap);

    ifstream KillMessageFile;
    KillMessageFile.open("data//objects//killmessages.txt");

    if (KillMessageFile.is_open())
    {
        KillMessages.clear();

        string strLine = "";

        while (!KillMessageFile.eof())
        {
            getline(KillMessageFile, strLine);

            KillMessages.push_back(strLine);
        }
    }

    if (iLocalPlayer > -1 && iLocalPlayer < Players.size())
    {
        iCurrentDirection = Players.at(iLocalPlayer)->getSnake().getDirection();
    }

    return 0;
}

void SnakeEngine::Update(Chatbox *cbxMessages)
{
    if (iLocalPlayer == -1 || gameType == GAMETYPE_NONE || bGameRunning == false || bGameFinished == true)
    {
        return;
    }

    iTimePassed += tmrFrame.getTicks();

    if (iTimePassed >= MOVEMENTINTERVAL)
    {
        int b = tmrFrame.getTicks();

        engineAI.HandleAI(Players, FoodObjects);

        for (int i = 0; i < Players.size(); i++)
        {
            Players.at(i)->Move(currentMap);

            HandlePlayerCollisions(i);
        }

        iTimePassed -= MOVEMENTINTERVAL;

        iCurrentDirection = Players.at(iLocalPlayer)->getSnake().getDirection();

        // Temporary replacement for the chase cam / jump cam switch
        Camera::Update(Players.at(iLocalPlayer)->getPlayerRect());

        printf("u: %i\n", tmrFrame.getTicks() - b);
    }

    /* TODO: Fix the chase cam
    if (iLocalPlayer != -1)
    {
        if (ConfigFile::bChaseCam = true)
        {
            Camera::UpdateChase(Players.at(iLocalPlayer)->getPlayerRect(), Players.at(iLocalPlayer)->getSnake().getDirection(), tmrFrame.getTicks());
        }
        else
        {
            Camera::Update(Players.at(iLocalPlayer)->getPlayerRect());
        }
    }
    */

    for (int i = 0; i < Messages.size(); i++)
    {
        cbxMessages->AddMessage("", Messages.at(i), GetSDLColor(255, 255, 255));
    }

    Messages.clear();

    tmrFrame.Start();
}

void SnakeEngine::HandlePlayerCollisions(const int& id)
{
    if (id < 0 || id >= Players.size())
    {
        return;
    }

    bool bReset = false;

    switch (currentMap.GetCollisionAtPoint(Players.at(id)->getPosition()))
    {
        case -1:
            bReset = true;
        break;

        case COLLISIONTYPE_CLOSED:
            bReset = true;
        break;
    }

    // Check for collisions with other snakes
    if (bReset == false)
    {
        for (int i = 0; i < Players.size(); i++)
        {
            for (int x = 0; x < Players.at(i)->getSnake().Body.size(); x++)
            {
                if (Players.at(id)->getPosition().x == Players.at(i)->getSnake().Body.at(x)->Position.x &&
                    Players.at(id)->getPosition().y == Players.at(i)->getSnake().Body.at(x)->Position.y)
                {
                    if (i == id && Players.at(i)->getSnake().Body.at(x)->Type == HEAD)
                    {
                        // Snake wont collide with it's own head
                    }
                    else
                    {
                        // Can't get kills from killing yourself
                        if (i != id)
                        {
                            Players.at(i)->AddKills(1);

                            string strKillMessage = KillMessages.at(rand() % KillMessages.size());

                            strKillMessage = StringReplace(strKillMessage, "A", Players.at(i)->getName());
                            strKillMessage = StringReplace(strKillMessage, "B", Players.at(id)->getName());

                            Messages.push_back(strKillMessage);
                        }
                        else // Suicide
                        {
                            Messages.push_back(Players.at(i)->getName() + " just ran into himself!");
                        }

                        Players.at(id)->AddDeaths(1);

                        CheckVictory(i);

                        bReset = true;
                    }
                }
            }
        }
    }

    if (bReset == true)
    {
        int iDirection = rand() % 4;

        Players.at(id)->Reset(currentMap.FindSpawnPosition(), iDirection, iSnakeStartSize);

        Players.at(id)->setScore(0);

        if (id == iLocalPlayer && iLocalPlayer != -1)
        {
            Camera::Update(Players.at(iLocalPlayer)->getPlayerRect());
        }

        bNeedScoreUpdate = true;
    }

    for (int i = 0; i < FoodObjects.size(); i++)
    {
        if (FoodObjects.at(i)->getPosition() == Players.at(id)->getPosition())
        {
            Players.at(id)->getSnake().AddSection();
            Players.at(id)->AddScore(FoodInfo::getTypeScore(FoodObjects.at(i)->getType()));
            FoodObjects.at(i)->setPosition(currentMap.FindSpawnPosition());
            FoodObjects.at(i)->setType(rand() % FoodInfo::getNumTypes());

            FoodEaten(i);

            CheckVictory(id);

            bNeedScoreUpdate = true;
        }
    }
}

void SnakeEngine::FoodEaten(int id)
{
    for (int i = 0; i < Players.size(); i++)
    {
        if (Players.at(i)->getTargetFood() == id)
        {
            // If they were targetting this food then set their target to -1
            // AI system will find them a new target on next update
            Players.at(i)->setTargetFood(-1);
        }
    }
}

int SnakeEngine::AddPlayer(const string& strName, const bool& bIsCPU)
{
    Players.push_back(new Player());

    int iDirection = rand() % 4;

    string strSetName = strName;

    if (bIsCPU == true)
    {
        iNumCPUs++;

        strSetName += "(" + lexical_cast<string>(iNumCPUs) + ")";
    }

    Players.back()->Initialise(currentMap.FindSpawnPosition(), SNAKE_WIDTH, SNAKE_HEIGHT, iDirection, iSnakeStartSize);
    Players.back()->setName(strSetName);
    Players.back()->setIsCPU(bIsCPU);

    return 0;
}

int SnakeEngine::RemovePlayer(const int& id)
{
    // If id is outside the bounds of the vector
    if (id < 0 || id >= Players.size()) { return -1; }

    delete Players.at(id);
    Players.erase(Players.begin() + id);

    return 0;
}

void SnakeEngine::Draw(SDL_Surface *surface)
{
    int b = tmrFrame.getTicks();

    if (bGameRunning == false) { return; }

    SDL_FillRect(World, NULL, 0x000000);

    SDL_Rect Worldclip, dstRect;

    Worldclip.x = Camera::GetView().x;
    Worldclip.y = Camera::GetView().y;
    Worldclip.w = ConfigFile::iCurrentWindowResX;
    Worldclip.h = ConfigFile::iCurrentWindowResY;

    SDL_SetClipRect(World, &Worldclip);

    // Draw layers 0 - 2 below the players
    currentMap.BlitLayerToSurface(World, 0, Worldclip);

    for (int i = 0; i < Players.size(); i++)
    {
        Players.at(i)->Draw(World);
    }

    for (int i = 0; i < FoodObjects.size(); i++)
    {
        FoodObjects.at(i)->Draw(World);
    }

    currentMap.BlitFromLayerToSurface(World, 1, Worldclip);

    dstRect.x = 0;
    dstRect.y = 0;
    SDL_BlitSurface(World , &Worldclip, surface, &dstRect);
}

InterfaceType SnakeEngine::HandleInput(const SDL_Event& event)
{
    switch (event.type)
    {
        case SDL_KEYDOWN:
        {
            int iReturn = HandleKeyDown(SDL_GetKeyName(event.key.keysym.sym), event.key.keysym.unicode);

            switch (iReturn)
            {
                case 2:
                    return GAMESUMMARY;
                break;

                case 3:
                    return MENU;
                break;
            }
        }
        break;

        case SDL_KEYUP:
        {
            iCurrentKeyPress = -1;
        }
        break;
    }

    return NONE;
}

int SnakeEngine::HandleKeyDown(string strKeyName, Uint16 UNICODEvalue)
{
    if (bGameRunning == false || iLocalPlayer == -1)
    {
        return -1;
    }

    if (bCPUOnlyGame == false)
    {
        switch (KeyBindings::GetKeyFromName(strKeyName))
        {
            case KB_UP:
            {
                if (iCurrentDirection != DOWN)
                {
                    Players.at(iLocalPlayer)->getSnake().setDirection(UP);
                }
            }
            break;

            case KB_DOWN:
            {
                if (iCurrentDirection != UP)
                {
                    Players.at(iLocalPlayer)->getSnake().setDirection(DOWN);
                }
            }
            break;

            case KB_LEFT:
            {
                if (iCurrentDirection != RIGHT)
                {
                    Players.at(iLocalPlayer)->getSnake().setDirection(LEFT);
                }
            }
            break;

            case KB_RIGHT:
            {
                if (iCurrentDirection != LEFT)
                {
                    Players.at(iLocalPlayer)->getSnake().setDirection(RIGHT);
                }
            }
            break;
        }
    }

    if (bGameFinished == true)
    {
        if (strKeyName == "space")
        {
            return 2;
        }
    }

    if (strKeyName == "escape")
    {
        return 3;
    }

    return 0;
}

vector<string> SnakeEngine::getScoreboardInfo()
{
    vector<string> Return;
    int iScore = 0;

    for (int i = 0; i < Players.size(); i++)
    {
        Return.push_back(Players.at(i)->getName());

        switch (gameType)
        {
            case GAMETYPE_KILL:
            {
                iScore = Players.at(i)->getKills();
            }
            break;

            case GAMETYPE_SCORE:
            {
                iScore = Players.at(i)->getScore();
            }
            break;
        }

        Return.back() += "\t" + lexical_cast<string>(iScore);
    }

    return Return;
}

void SnakeEngine::setLocalPlayer(const string& strNewLocalPlayer)
{
    for (int i = 0; i < Players.size(); i++)
    {
        if (Players.at(i)->getName() == strNewLocalPlayer)
        {
            iLocalPlayer = i;
        }
    }
}

int SnakeEngine::getLocalPlayer()
{
    return iLocalPlayer;
}

int SnakeEngine::getNumPlayers()
{
    return Players.size();
}

bool SnakeEngine::getNeedScoreUpdate()
{
    return bNeedScoreUpdate;
}

void SnakeEngine::setNeedScoreUpdate(const bool& bNewVal)
{
    bNeedScoreUpdate = bNewVal;
}

int SnakeEngine::getScoreToWin()
{
    return iScoreToWin;
}

void SnakeEngine::CheckVictory(const int& id)
{
    if (id < 0 || id >= Players.size())
    {
        return;
    }

    int iScore = 0;

    switch (gameType)
    {
        case GAMETYPE_KILL:
        {
            iScore = Players.at(id)->getKills();
        }
        break;

        case GAMETYPE_SCORE:
        {
            iScore = Players.at(id)->getScore();
        }
        break;
    }

    if (iScore >= iScoreToWin)
    {
        iWinner = id;
        bGameFinished = true;

        SummaryInfo::FreeInfo();

        for (int i = 0; i < Players.size(); i++)
        {
            SummaryInfo::AddPlayerInfo(Players.at(i), (i == id) ? true : false);
        }
    }
}
